// Copyright (c) Appeon. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.

using System;
using System.Data;
using System.Linq;

namespace SnapObjects.Data
{
    internal class ModelCommandBuilder : CommandBuilder
    {
        public ModelCommandBuilder(DataContext dataContext)
            : base(dataContext, new CommandBuilderContext())
        {
        }

        public ModelCommandBuilder(DataContext dataContext, CommandBuilderContext builderContext)
            : base(dataContext, builderContext)
        {
        }

        public bool BuildCommand(ModelUpdateInfo modelUpdateInfo)
        {
            this.Init();

            if (modelUpdateInfo.ModelState == ModelState.Deleted || modelUpdateInfo.HasWrite)
            {
                var resultSetMapping = this.BuildSql(modelUpdateInfo, 0);

                this.AddResultSetMapping(resultSetMapping);

                this.BuildParameters(modelUpdateInfo);

                return true;
            }

            return false;
        }

        public ResultSetMapping BuildSql(ModelUpdateInfo modelUpdateInfo)
        {
            return this.BuildSql(modelUpdateInfo, 0);
        }

        public void BuildParameters(ModelUpdateInfo modelUpdateInfo)
        {
            this.BuildUseCurrentValueParameters(modelUpdateInfo);

            this.BuildUseOriginalValueParameters(modelUpdateInfo);

            this.AddOutputParameters(modelUpdateInfo);
        }

        public int GetParameterCount(ModelUpdateInfo modelUpdateInfo)
        {
            var currentValueParameterCount = 
                modelUpdateInfo.ColumnModifications.Count(m => m.UseCurrentValueParameter);

            var originalValueParameterCount = modelUpdateInfo.ColumnModifications
                .Count(m => m.UseOriginalValueParameter);

            var outputParameterCount = modelUpdateInfo.OutputParameters != null ?
                modelUpdateInfo.OutputParameters.Count : 0;

            return currentValueParameterCount + originalValueParameterCount + outputParameterCount;
        }

        private ResultSetMapping BuildSql(ModelUpdateInfo modelUpdateInfo, int sqlPosition)
        {
            var resultSetMapping = ResultSetMapping.NoResultSet;
            var updateSqlGenerator = _dataContext.AdoDbSqlProvider.UpdateSqlGenerator;

            if (modelUpdateInfo.ModelState == ModelState.NewModified)
            {
                resultSetMapping = updateSqlGenerator.AppendInsertSql(
                                        this.SqlBuilder, modelUpdateInfo, sqlPosition);
            }
            else if (modelUpdateInfo.ModelState == ModelState.Deleted)
            {
                resultSetMapping = updateSqlGenerator.AppendDeleteSql(
                                        this.SqlBuilder, modelUpdateInfo, sqlPosition);
            }
            else if (modelUpdateInfo.ModelState == ModelState.Modified)
            {
                resultSetMapping = updateSqlGenerator.AppendUpdateSql(
                                        this.SqlBuilder, modelUpdateInfo, sqlPosition);
            }

            return resultSetMapping;
        }

        private void AddOutputParameters(ModelUpdateInfo modelUpdateInfo)
        {
            if (modelUpdateInfo.OutputParameters?.Count > 0)
            {
                foreach (var readColumn in modelUpdateInfo.OutputParameters)
                {
                    this.AddParameter(readColumn.Key.ParameterName,
                        readColumn.Key.Value,
                        readColumn.Key.ClrType,
                        readColumn.Key.Direction,
                        String.Empty,
                        ((AdoDbParameter)readColumn.Key).ValueConverter);
                }
            }
        }

        private void BuildUseCurrentValueParameters(ModelUpdateInfo modelUpdateInfo)
        {
            var sqlGenerationHelper = _dataContext.AdoDbSqlProvider.SqlGenerationHelper;

            var currentValueColumns = modelUpdateInfo.ColumnModifications
                .Where(m => m.UseCurrentValueParameter);

            foreach (var currentValueColumn in currentValueColumns)
            {
                var parameterName = sqlGenerationHelper.GenerateParameterName(
                    currentValueColumn.ParameterName);

                this.AddParameter(
                    parameterName,
                    currentValueColumn.CurrentValue,
                    currentValueColumn.Property.DataType,
                    ParameterDirection.Input,
                    currentValueColumn.Property.TypeName,
                    currentValueColumn.Property.ValueConverter);
            }
        }

        private void BuildUseOriginalValueParameters(ModelUpdateInfo modelUpdateInfo)
        {
            var sqlGenerationHelper = _dataContext.AdoDbSqlProvider.SqlGenerationHelper;

            var originalValueColumns = modelUpdateInfo.ColumnModifications
                .Where(m => m.UseOriginalValueParameter);

            foreach (var originalValueColumn in originalValueColumns)
            {
                var parameterName = sqlGenerationHelper.GenerateParameterName(
                    originalValueColumn.OriginalParameterName);

                this.AddParameter(
                    parameterName,
                    originalValueColumn.OriginalValue,
                    originalValueColumn.Property.DataType,
                    ParameterDirection.Input,
                    originalValueColumn.Property.TypeName,
                    originalValueColumn.Property.ValueConverter);
            }
        }
    }
}
